/**
 * MIT License
 * 
 * Copyright (c) 2024 - present @ ebraid
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef __EB_SPI_H__
#define __EB_SPI_H__

#include <kernel/device.h>
#include <kernel/driver.h>
#include <kernel/syslog.h>
#include <kernel/fdt.h>
#include <drivers/platform.h>
#include <drivers/gpio.h>


#define  SPI_MODE_CPOL_MASK   0x02
#define  SPI_MODE_CPHA_MASK   0x01


#define  SPI_MODE_0      0x00 // CPOL=0, CPHA=0
#define  SPI_MODE_1      0x01 // CPOL=0, CPHA=1
#define  SPI_MODE_2      0x02 // CPOL=1, CPHA=0
#define  SPI_MODE_3      0x03 // CPOL=1, CPHA=1


/**
 * @brief spi bus, it define in the spi.c file
 */
extern struct bus_type spi_bus;
struct spi_driver;
struct spi_device;


/**
 * @brief spi transfer struct
 * @tx_buf: transmit buffer
 * @rx_buf: receive buffer
 * @len: transfer length
 */
struct spi_transfer {
    void *tx_buf;
    void *rx_buf;
    size_t len;
};


/**
 * @brief spi gpio pin struct
 * @mosi: mosi pin
 * @miso: miso pin
 * @clk: clk pin
 * @cs: cs pin, max 5 slave device
 */
struct spi_gpio {
    int16_t mosi;
    int16_t miso;
    int16_t clk;
    int16_t cs[5];
};


/**
 * @brief spi master struct
 * @io_base: spi controller base address
 * @clock_freq_hz: spi controller clock frequency
 * @pin: spi gpio pin
 * @transfer: transfer function
 * @set_cs: set cs function
 * @setup: setup function
 */
struct spi_master {
    size_t io_base;
    size_t clock_freq_hz;
    struct spi_gpio pin;
    int (*transfer)(struct spi_device *spi, struct spi_transfer *xfer);
    void (*set_cs)(struct spi_device *spi, bool enable);
    void (*setup)(struct spi_device *spi);
};


/**
 * @brief spi device struct
 * @device: device struct
 * @master: point to spi master
 * @speed: spi speed
 * @mode: spi mode
 * @duplex: spi duplex, 0: full duplex, 1: half duplex
 * @lsb: lsb first, 0: msb first, 1: lsb first
 * @client: client mode, 0: master mode 1: client mode
 * @bits: spi bits mode, 8: 8 bits, 16: 16 bits
 * @addr: device address
 * @platform_data: platform data
 */
struct spi_device {
    struct device     device;
    struct spi_master *master;
    uint32_t          speed;
    uint8_t           mode: 2;
    uint8_t           duplex: 1;
    uint8_t           lsb: 1;
    uint8_t           client: 1;
    uint8_t           bits;
    uint16_t          addr;
    uint32_t          irq_num;
    void             *private_data;
};


/**
 * @brief spi driver struct
 * @driver: driver struct
 * @probe: probe function
 * @remove: remove function
 * @of_match_table: id table
 */
struct spi_driver {
    struct driver   driver;
    int (*probe)(struct spi_device *dev);
    void (*remove)(struct spi_device *dev);
};


#ifdef __cplusplus
extern "C" {
#endif


#define  SPI_SYSLOG_INFO(...)   SYSLOG_INFO("SPI: "__VA_ARGS__)
#define  SPI_SYSLOG_ERROR(...)  SYSLOG_ERROR("SPI: "__VA_ARGS__)
#define  SPI_SYSLOG_WARN(...)   SYSLOG_WARN("SPI: "__VA_ARGS__)
#define  SPI_DEBUG_TRACE(...)   DEBUG_TRACE("SPI: "__VA_ARGS__)


/**
 * @brief get spi device from device struct
 * @param dev: device struct
 * @return: spi device
 */
static inline  struct spi_device* to_spi_device(struct device *dev)
{
    return container_of(dev, struct spi_device, device);
}


/**
 * @brief get spi driver from driver struct
 * @param drv: driver struct
 * @return: spi driver
 */
static inline  struct spi_driver* to_spi_driver(struct driver *drv)
{
    return container_of(drv, struct spi_driver, driver);
}


/**
 * @brief register spi device
 * @param dev: spi device
 * @return: 0: success, -1: failed
 */
static inline int spi_device_register(struct spi_device *dev)
{
    dev->device.bus = &spi_bus;
    return device_register(&dev->device);
}


/**
 * @brief unregister spi device
 * @param dev: spi device
 * @return: none
 */
static inline void spi_device_unregister(struct spi_device *dev)
{
    device_unregister(&dev->device);
}


/**
 * @brief register spi driver
 * @param drv: spi driver
 * @return: 0: success, -1: failed
 */
static inline int spi_driver_register(struct spi_driver *drv)
{
    drv->driver.bus = &spi_bus;
    return driver_register(&drv->driver);
}


/**
 * @brief unregister spi driver
 * @param drv: spi driver
 * @return: none
 */
static inline void spi_driver_unregister(struct spi_driver *drv)
{
    driver_unregister(&drv->driver);
}


/**
 * @brief set spi device driver data
 * @param dev: spi device
 * @param data: driver data
 * @return: none
 */
static inline void spi_set_drvdata(struct spi_device *dev, void *data)
{
    device_set_drvdata(&dev->device, data);
}


/**
 * @brief get spi device driver data
 * @param dev: spi device
 * @return: driver data
 */
static inline void* spi_get_drvdata(struct spi_device *dev)
{
    return device_get_drvdata(&dev->device);
}


/**
 * @brief set spi device platform data
 * @param dev: spi device
 * @param data: platform data
 * @return: none
 */
static inline void spi_set_platform_data(struct spi_device *dev, const void *data)
{
    device_set_platform_data(&dev->device, data);
}


/**
 * @brief get spi device platform data
 * @param dev: spi device
 * @return: platform data
 */
static inline const void* spi_get_platform_data(struct spi_device *dev)
{
    return device_get_platform_data(&dev->device);
}


/**
 * @brief set spi device private data
 * @param dev: spi device
 * @param data: private data
 * @return: none
 */
static inline void spi_set_private_data(struct spi_device *dev, void *data)
{
    dev->private_data = data;
}


/**
 * @brief get spi device private data
 * @param dev: spi device
 * @return: private data
 */
static inline void* spi_get_private_data(struct spi_device *dev)
{
    return dev->private_data;
}


/**
 * @brief get cs pins from device tree
 * @param node: device tree node
 * @param cs_pins: spi_gpio cs pins
 * @return: 0: success, -1: failed
 */
int spi_get_cs_pins(fdt_node_t *node, int16_t *cs_pins);


/**
 * @brief get gpio pin from device tree
 * @param node: device tree node
 * @param pin: spi_gpio pin
 * @return: 0: success, -1: failed
 */
int spi_master_get_gpio_from_fdt(fdt_node_t *node, struct spi_gpio *pin);


/**
 * @brief get spi device config from device tree
 * @param node: device tree node
 * @param spi_dev: spi device
 * @return: 0: success, -1: failed
 */
int spi_device_get_config_from_fdt(fdt_node_t *node, struct spi_device *spi_dev);


/**
 * @brief spi gpio set cs
 * @param spi: spi device
 * @param enable: enable or disable
 * @return: none
 */
static inline void spi_gpio_set_cs(struct spi_device *spi, bool enable)
{
    struct spi_master *master = spi->master;
    if(master->pin.cs[spi->addr] < 0 && spi->addr >= 5) {
        return;
    }

    if(enable) {
        gpio_set_value(master->pin.cs[spi->addr], IO_LOW);
    }
    else {
        gpio_set_value(master->pin.cs[spi->addr], IO_HIGH);
    }
}


/**
 * @brief set spi speed
 * @param spi: spi device
 * @param speed: spi speed
 */
static inline void spi_set_speed(struct spi_device *spi, uint32_t speed)
{
    spi->speed = speed;
    spi->master->setup(spi);
}


#ifdef __cplusplus
}
#endif


#endif //!__EB_SPI_H__
